package main;

import java.util.ArrayList;
import java.util.List;

public class Populacao {
	//lista de individuos que fazem parte da populacao
	List<Individuo> individuos;
	private static int ultimoID;
	
	public Populacao(){
		this.individuos = new ArrayList<Individuo>();
		ultimoID = 0;
	}
	
	//insere individuo na lista de individuos da populacao
	public void setIndividuos(List<Individuo> selecionados){
		
		//individuos selecionado sao adicionados ah populacao
		this.individuos = selecionados;
	}
	
	private int generateNextID(){
		ultimoID ++;
		return ultimoID;
	}
	
	public Populacao clone(){
		//cria nova populacao
		Populacao clones = new Populacao();
		List<Individuo> cloned = new ArrayList<Individuo>();
		Individuo novoIndividuo;
		for(Individuo i : this.individuos){
			novoIndividuo = i.clone();
			cloned.add(novoIndividuo);
		}
		clones.individuos = cloned;
		return clones;
	}
	
	//gera e retorna a primeira populacao
	public Populacao primPop(World world){
		
		//limite de tempo (ms) de execucao do loop de geracao de individuos
		Long limite = new Long(100);
		
		
		//pega tempo de inicio em milisegundos
		Long inicio = System.currentTimeMillis();		
		
		//inicializa variavel auxiliar que sera passada para metodo gerador de individuo
		//os indices i contem true se o ponto i+1 ainda nao foi coberto e false se
		//o ponto ja foi coberto
		List<Boolean> pointsAux = new ArrayList<Boolean>();
		for (int i=0; i < world.qtdPonto; i++)
			pointsAux.add(i, true);
		
		
		//variavel auxiliar (lista) para ir armazenando os individuos gerados
		List<Individuo> gerados = new ArrayList<Individuo>();
		
		//tempo passado em segundos desde a chamada do metodo
		Long tempoPassado = System.currentTimeMillis() - inicio; 
		//gera individuos durante 100  milisegundos
		while(tempoPassado < limite){
			
			Individuo novoIndividuo = new Individuo().genIndividuo(world,pointsAux);
			if(novoIndividuo != null){
				int id = this.generateNextID();
				novoIndividuo.setId(id);
				
				//add criador ao novo individuos
				novoIndividuo.criador="primPop";
				
				gerados.add(novoIndividuo);
			}
			tempoPassado = System.currentTimeMillis() - inicio;
		}
		
		gerados = this.mataRepetidos(gerados);
		this.setIndividuos(gerados);
		return this;
	}
	
	public void imprimeIndividuos(){
	
		for(int i = 0; i < this.individuos.size(); i ++)
			this.individuos.get(i).printIndividuo();
	}
	
	/*
	 * Prototipo de metodo para eliminar individuos repetidos - VERIFICAR!!
	 */
	public List<Individuo> mataRepetidos(List<Individuo> original){
		
		Individuo atual;
		for(int i = 0; i < original.size(); i++){
			atual = original.get(i);
			for (int j = i+1; j < original.size(); j++)
				if(atual.stations.equals(original.get(j).stations))
					original.remove(j);
		}
		return original;
		
	}

	/**
	 * Gera uma nova populacao alterando individuos internos
	 * @return
	 * 	Retorna uma população nova com uma porcentagem de indivíduos alterados aleatoraimente
	 */
	public Populacao mutacao(World world, int porcentagem, double probabilidadeAdicionar){
		//cria uma populacao clonada
		Populacao antigaPopulacao = this;
		Populacao novaPopulacao = new Populacao();
		int qtdIndividuos = this.individuos.size();
		int qtdIndividuosAlterados = (porcentagem * qtdIndividuos)/100;
		int indiceAlteracaoAtual=0; 
		//inicia a mutacao do individuos
		for(int i=0; i < qtdIndividuosAlterados; i++){
			// escolhe aleatoriamente quem ira alterar
			indiceAlteracaoAtual = ((int)(Math.random()*qtdIndividuos));
			//obtem o individuo que sofrera alteracao e clona para depois alterar
			Individuo mutante = antigaPopulacao.individuos.get(indiceAlteracaoAtual).clone();
			
			
			//percorre a lista de estacoes que este individuo possui e faz alteracoes aleatorias
			int indiceMutante = ((int)(Math.random()*world.qtdPonto));
				double k = Math.random(); //regula a quantidade de true *****************************************
				//altera as estacoes do mutante
				if(k < probabilidadeAdicionar){//em 20% dos casos adiciona
					mutante.stations.set(indiceMutante, true);
				}else{
					mutante.stations.set(indiceMutante, false);
				}
			//Se mutante for invalido
			//decrementa contador para que seja gerado novamente
			if(!mutante.isValid(world)){
				i--;
			}else{
				//senao adiciona a populacao nova
				mutante.calculaCusto(world);
				//atualiza estacoes true do mutante
				mutante.setTrueStations(mutante.stations);
				//add criador ao novo individuo
				mutante.criador="mutacao";
				
				novaPopulacao.individuos.add(mutante);
			}
		}
		return novaPopulacao;
	}
	
	public void mutacaoMelhor(World w){
		//clona melhor individuo
		Individuo melhor = this.individuos.get(0).clone();
		melhor = melhor.limpaStations(w);
		melhor.setTrueStations(melhor.stations);
		melhor.calculaCusto(w);
		melhor.criador = "mutacaoMelhor";
		if(melhor.custo < this.individuos.get(0).custo)
			this.individuos.set(0, melhor);
	}
	
	//Gera uma nova populacao cruzando pares de individuos aleatoriamente
	public Populacao cruzamento(World world, int porcentagem){
		Populacao antigaPopulacao = this;
		Populacao novaPopulacao = new Populacao();

		int qtdIndividuos = this.individuos.size();
		int qtdIndividuosAlterados = (porcentagem * qtdIndividuos)/100;

		int i1=0,i2=0;//indice dos individuos q serao usados para o cruzamento
		int k=0;
		List<Integer> naoVisitados=new ArrayList<Integer>();;
		
		for(int i=0; i<qtdIndividuos; i++)
			naoVisitados.add(i);
		
		//inicia o cruzamento do individuos
		while(k < qtdIndividuosAlterados){
			

			// escolhe aleatoriamente quem ira alterar
				i1 = naoVisitados.get((int)(Math.random()*100000)%naoVisitados.size());
				do{
				i2 =naoVisitados.get((int)(Math.random()*100000)%naoVisitados.size());		
				//continua se i2<>i1 
				}while(i2==i1);
			
			
			//cria individuos filhos para o cruzamento
			Individuo f3= new Individuo();
			/* algoritmo para cruzamento*/
			
			f3=antigaPopulacao.individuos.get(i1).filhoOr(antigaPopulacao.individuos.get(i2));
			
			//limpaStations ja atualiza estacoesTrue
			f3 = f3.limpaStations(world);
			f3.setId(k);
			f3.calculaCusto(world);
			
			//add criador ao novo individuos
			f3.criador="cruzamento f3";
			
			novaPopulacao.individuos.add(f3);
			k++;
		}
		
		return novaPopulacao;
	}
}